package com.lau.common.aspect;


import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson2.JSON;
import com.lau.common.annotation.OperationLog;
import com.lau.common.enums.StatusCode;
import com.lau.common.factory.AsyncFactory;
import com.lau.common.factory.AsyncManager;
import com.lau.entity.SysOperLog;
import com.lau.security.LoginUser;
import com.lau.utils.SecurityUtils;
import com.lau.utils.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Map;

/**
 * @author:Lau
 * @create: 2024-01-17 16:18
 * @Description: 操作日志切面
 */
@Component
@Aspect
@Slf4j
public class OperationLogAspect {
    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");

    /**
     * 定义切点,切点为使用@OperationLog注解的方法
     */
    @Pointcut("@annotation(com.lau.common.annotation.OperationLog)")
    private void pointCut() {
    }


    /**
     * 前置通知：目标方法执行之前执行以下方法体的内容。
     * value：绑定通知的切入点表达式。可以关联切入点声明，也可以直接设置切入点表达式
     * <br/>
     * * @param joinPoint：提供对连接点处可用状态和有关它的静态信息的反射访问<br/> <p>
     * * * Object[] getArgs()：返回此连接点处（目标方法）的参数，目标方法无参数时，返回空数组
     * * * Signature getSignature()：返回连接点处的签名。
     * * * Object getTarget()：返回目标对象
     * * * Object getThis()：返回当前正在执行的对象
     * * * StaticPart getStaticPart()：返回一个封装此连接点的静态部分的对象。
     * * * SourceLocation getSourceLocation()：返回与连接点对应的源位置
     * * * String toLongString()：返回连接点的扩展字符串表示形式。
     * * * String toShortString()：返回连接点的缩写字符串表示形式。
     * * * String getKind()：返回表示连接点类型的字符串
     * * * </p>
     */
    @Before(value = "@annotation(operationLog)")
    public void boBefore(JoinPoint joinPoint, OperationLog operationLog) {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(operationLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, OperationLog operationLog, Object jsonResult) {
        handleLog(joinPoint, operationLog, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "@annotation(operationLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OperationLog operationLog, Exception e) {
        handleLog(joinPoint, operationLog, e, null);
    }

    /**
     * 日志处理
     *
     * @param joinPoint 切点
     * @param operationLog 注解
     * @param e 异常
     * @param jsonResult 返回值
     */
    protected void handleLog(final JoinPoint joinPoint, OperationLog operationLog, final Exception e, Object jsonResult) {
        LoginUser loginUser = null;
        try{
            //获取当前用户
            loginUser = SecurityUtils.getLoginUser();
        }catch (Exception ex){
            ex.printStackTrace();
            return;
        }
        //日志记录
        SysOperLog operLog = new SysOperLog();
        operLog.setOperId(loginUser.getUserId());
        operLog.setOperTime(LocalDateTime.now());
        if(e==null){
            operLog.setErrorMsg(null);
            operLog.setOperStatus(StatusCode.SUCCESS.getCode());
        }else{
            operLog.setErrorMsg(StringUtils.substring(e.getMessage(),0,200));
            operLog.setOperStatus(StatusCode.FAIL.getCode());
        }
        //耗时
        operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
        //获取用户ip
        operLog.setOperIp(ServletUtil.getClientIP(ServletUtils.getRequest()));
        // 设置方法名称
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        operLog.setOperMethod(className + "." + methodName + "()");
        //请求的url
        operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
        //获取注解中对方法的描述信息
        operLog.setEvent(operationLog.event());
        operLog.setOperType(operationLog.operType().getCode());
        if(operationLog.isSaveRequestData()){
            //获取请求参数
            Map<String, String> paramMap = ServletUtil.getParamMap(ServletUtils.getRequest());
            //移除前端请求中的时间后缀
            paramMap.remove("t");
            operLog.setParameter(StringUtils.substring(JSON.toJSONString(paramMap),0,2000));
            operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult),0,2000));
        }
        log.info(operLog.toString());
        AsyncManager.me().execute(AsyncFactory.recordOperLog(operLog));
    }
}
